package client;

import java.awt.Point;
import java.net.Socket;
import java.util.Vector;

import javax.swing.JOptionPane;

import nanoxml.XMLElement;
import utils.AbstractConnectionThread;
import utils.ITableConnection;
import utils.ITransmittable;
import utils.Player;
import utils.PlayerList;
import utils.Settings;
import utils.Table;
import utils.User;

import commands.CommandArmy;
import commands.CommandChatMessage;
import commands.CommandGameResult;
import commands.CommandLeaveTable;
import commands.CommandMessageToUser;
import commands.CommandMove;
import commands.CommandNewUserJoinTable;
import commands.CommandPlayerDisconnected;
import commands.CommandReadyToStart;
import commands.CommandWeaponSelection;
import commands.Commands;

import enums.PlayerColor;
import enums.Weapon;

/**
 * Connects the players and viewers to the game owner
 */
public class ConnectionThreadTableClientToOwner extends AbstractConnectionThread implements ITableConnection {

	private Table table = null;
	private TableFrame tableFrame = null; 
	private Player player = null;
	private Model model = null;
	private IGameConnection connRPSClientToServer = null;
	
	
	public ConnectionThreadTableClientToOwner (Socket socket, String username, IGameConnection connRPSClientToServer) {
		super(socket);
		
		this.username = username;
		this.connRPSClientToServer = connRPSClientToServer;
	}

	@Override
	public void parse(XMLElement command) {
		try {
			if (command.getName().equals(Commands.TABLE)) {
				table = new Table(command);
				player = table.getPlayerList().getPlayer(username);
				
				tableFrame = new TableFrame(this, connRPSClientToServer.getTableList());
				model = tableFrame.model;
			}
			else if (command.getName().equals(Commands.LEAVE_TABLE)) {
				CommandLeaveTable commandLeaveTable = new CommandLeaveTable(command);
				String usernameLeftTable = commandLeaveTable.getUsername();
			
				Player playerLeaveTable = table.getPlayerList().getPlayer(usernameLeftTable);
				
				// Check if the player that left is an active player (not a viewer) and that the game still continues.
				// Only if so, show a message about him leaving and the game ending
				if (playerLeaveTable != null && playerLeaveTable.isViewer() == false && model.isInPlay())
				{
					// Freeze the timer
					tableFrame.freezeCountDown();
					
					PlayerColor colorLeaveTable = playerLeaveTable.getColor();
					String message = "";
					String winner = "", losser = "";
					// If the user is a player, then it means the other player has quit and the user has won
					if (player.isViewer() == false) {
						message += "The other player quit the game. You won !";
						
						if (usernameLeftTable.equals(table.getOtherPlayerUsername()))
						{
							winner = table.getOwnerUsername();
							losser = table.getOtherPlayerUsername();
							table.setOtherPlayerUsername("");
						} 
						else
						{
							winner = table.getOtherPlayerUsername();
							losser = table.getOwnerUsername();
						}
					}
					// Else, the user is a viewer. Notify him which player quit and which won
					else {
						message += "The " + colorLeaveTable.toString() + " player has quit, which means that the " +
						PlayerColor.flipColor(colorLeaveTable).toString() + " player has won !";
					}
					
					tableFrame.endGame();
					CommandGameResult commandGameResult = new CommandGameResult(winner ,losser);
					connRPSClientToServer.send(commandGameResult);
					JOptionPane.showMessageDialog(tableFrame , message, "The Game Has Ended" , JOptionPane.INFORMATION_MESSAGE);
				}
				// Check if the owner left the game and the game wasn't even started
				else if (usernameLeftTable.equals(table.getOwnerUsername()) && model.getGameState() == Model.GAME_STATE_NOT_PLAYING)
				{
					// Disable the start button
					tableFrame.disableStartButton();
					
					// Notify the user that the owner left the game before it even started
					String message = "The owner of this table has quit the game before it even started." + Settings.NEW_LINE +
					"Please try another table.";
					JOptionPane.showMessageDialog(tableFrame, message, "Game Ended", JOptionPane.INFORMATION_MESSAGE);
				}
				
				table.getPlayerList().removePlayer(usernameLeftTable);
			}
			else if (command.getName().equals(Commands.NEW_USER_JOIN_TABLE)) {
				CommandNewUserJoinTable commandNewUserJoinTable = new CommandNewUserJoinTable(command);
				String joinedUsername = commandNewUserJoinTable.getUsername();				
				table.getPlayerList().addPlayer(new User(joinedUsername) , PlayerColor.BLUE);
				
				// Check if we are the owner
				if (model.isOwner())
				{
					// Send the joining user the data about the table
					sendToUser(joinedUsername, table);
					
					//  If the game state is not NOT_PLAYING, send also the model to the user that joined
					if (model.getGameState() != Model.GAME_STATE_NOT_PLAYING)
					{
						sendToUser(joinedUsername, model);
					}
				}
				
				// Check if we are waiting for a disconnected user to return
				if (model.getGameState() == Model.GAME_STATE_WAITING_FOR_PLAYER)
				{
					// Check if the user that has just joined is the player we are waiting for
					if (joinedUsername.equals(table.getOtherPlayerUsername()))
					{
						model.returnGameStateToBeforePlayerDisconnected();
						
						tableFrame.freezeCountDown();
						tableFrame.showMessagePlayerReturned(joinedUsername);
						tableFrame.setCountDownForGame();
						
						// Set the player as playing
						table.getPlayerList().getPlayer(joinedUsername).startGame();
					}
				}
			}
			else if (command.getName().equals(Commands.CHAT_MESSAGE)) {
				CommandChatMessage commandChatMessage= new CommandChatMessage(command);	
				tableFrame.receiveGameChat(commandChatMessage);
			}
			else if (command.getName().equals(Commands.READY_TO_START)) {
				readyToStart(new CommandReadyToStart(command));
			}
			else if (command.getName().equals(Commands.MODEL)) {
				if (table.getGameState() != Table.NOT_PLAYING)
				{ 	
					tableFrame.setModel(command);
					model = tableFrame.getModel();
					
					// Check which of the armies we got
					model.gotEnemyArmy = false;
					if (model.getPiece(Board.NUM_OF_COLS - 1, Board.NUM_OF_ROWS - 1).weapon != Weapon.NONE && model.getGameState() != Model.GAME_STATE_BUILD_ARMY)
					{
						model.gotEnemyArmy = true;
					}
					
					if (model.getGameState() != Model.GAME_STATE_NOT_PLAYING && model.getGameState() != Model.GAME_STATE_PLAYING &&
						model.getGameState() != Model.GAME_STATE_GAME_OVER && model.getGameState() != Model.GAME_STATE_WAITING_FOR_PLAYER &&
						model.getPiece(0, 0).weapon != Weapon.NONE)
					{
						model.setGameState(Model.GAME_STATE_FINISHED_BUILDING_ARMY);
					}
					
					String message = "";
					
					// Check if we are waiting for a disconnected user to return
					if (model.getGameState() == Model.GAME_STATE_WAITING_FOR_PLAYER)
					{
						// Check if the user that has just joined and received the model is the player we are waiting for
						if (username.equals(table.getOtherPlayerUsername()))
						{
							model.returnGameStateToBeforePlayerDisconnected();
							
							// We are getting the game state of the table owner, so if he had already placed the flag, trap or armed his army,
							// the returned user will be in that state too, even if hadn't done those things. So in any case, set him to choose the flag
							if (model.getGameState() == Model.GAME_STATE_PLACING_TRAP || model.getGameState() == Model.GAME_STATE_BUILD_ARMY ||
								model.getGameState() == Model.GAME_STATE_FINISHED_BUILDING_ARMY)
							{
								if (model.getPiece(0, 0).weapon == Weapon.NONE)
								{
									model.setGameState(Model.GAME_STATE_PLACING_FLAG);
								}
								else
								{
									model.setGameState(Model.GAME_STATE_FINISHED_BUILDING_ARMY);
								}
							}
							
							// Set the player as playing
							table.getPlayerList().getPlayer(username).startGame();
							
							message = "Welcom back, " + username + " ! We have been waiting for you." + Settings.NEW_LINE +
									  "Let's continue the game.";
							if (tableFrame.isCountdownVisible())
							{
								message += Settings.NEW_LINE + "(You will not see the time limitation for the first turn)";
							}
							JOptionPane.showMessageDialog(tableFrame, message, "Welcome To The Game", JOptionPane.INFORMATION_MESSAGE);
						}
						else
						{
							// Notify to the user who has just joined and received the model, that we are waiting for a player
							message = "Welcome " + username + " ! We are currently waiting for a disconnected player to return";
							JOptionPane.showMessageDialog(tableFrame, message, "Welcome To The Game", JOptionPane.INFORMATION_MESSAGE);
						}
					}

					// Whether the user was disconnected or not, if we are now in the middle of a game, activate the timer
					if (model.getGameState() == Model.GAME_STATE_PLAYING)
					{
						if (table.getCurrentPlayer().toString().toLowerCase().equals("blue"))
						{
							tableFrame.switchTurn();
						}
						tableFrame.labelWhoseTurn.setVisible(true);
						tableFrame.setCountDownForGame();
						
						if (message.isEmpty())
						{
							message = "You have joined a game in progress. ";
							if (tableFrame.isCountdownVisible())
							{
								message += Settings.NEW_LINE + "(You will not see the time limitation for the first turn)";
							}
							JOptionPane.showMessageDialog(tableFrame, message, "Welcome To The Game", JOptionPane.INFORMATION_MESSAGE);
						}
					}
					
					if (model.getGameState() != Model.GAME_STATE_NOT_PLAYING)
					{
						tableFrame.disableStartButton();
					}
				}
			}
			else if (command.getName().equals(Commands.WEAPON_SELECTION)) {
				CommandWeaponSelection commandWeaponSelection= new CommandWeaponSelection(command);
				
				if (player.isViewer() == false)
				{
					model.enemyWeaponSelect = commandWeaponSelection.getWeapon();
					if (model.selectedWeapon != Weapon.NONE) {
						model.WarDrawSemaphore.release();
					}
				}
				else
				{
					// If the user who selected the weapon is the owner, the viewer will treat this as if the enemy chose it
					if (table.isPlayerOwner(commandWeaponSelection.getUsername()))
					{
						model.enemyWeaponSelect = commandWeaponSelection.getWeapon();
					}
					// Else, if the other player chose the weapon, the viewer will treat it as if he himself chose it
					else
					{
						model.selectedWeapon = commandWeaponSelection.getWeapon();
					}
					
					if (model.enemyWeaponSelect != Weapon.NONE && model.selectedWeapon != Weapon.NONE)
					{
						model.WarDrawSemaphore.release();
					}
				}
			}
			else if (command.getName().equals(Commands.ARMY)) {
				CommandArmy commandArmy = new CommandArmy(command);
				
				// If the user is a player, then it received the enemy's army
				if (player.getPlayerState() == Player.PLAYER_STATE_PLAYING)
				{
					model.setEnemyArmy(commandArmy.getArmy());

					if (model.getGameState() == Model.GAME_STATE_FINISHED_BUILDING_ARMY) {
						model.setGameState(Model.GAME_STATE_PLAYING);
						tableFrame.labelWhoseTurn.setVisible(true);
						tableFrame.startCountDown();
					}
					else
						model.gotEnemyArmy = true;
				}
				// Else, if the user is a viewer, it will receive each of the armies and should update both
				else
				{
					// If the army is the table owner's army, then the viewer sets it as the enemy's army
					if (commandArmy.getUsername().equals(table.getOwnerUsername()))
					{
						model.setEnemyArmy(commandArmy.getArmy());
						
						model.gotEnemyArmy = true;
					}
					// Else, if the army is the other player's army, the viewer sets it as his own
					else
					{
						model.setOwnArmy(commandArmy.getArmy());

						model.setGameState(Model.GAME_STATE_FINISHED_BUILDING_ARMY);
					}
					
					if (model.getGameState() == Model.GAME_STATE_FINISHED_BUILDING_ARMY &&
						model.gotEnemyArmy)
					{
						model.setGameState(Model.GAME_STATE_PLAYING);
						tableFrame.labelWhoseTurn.setVisible(true);
						tableFrame.startCountDown();
					}
					
					tableFrame.board.repaint();
				}
			}
			else if (command.getName().equals(Commands.MOVE)) {
				CommandMove commandMove = new CommandMove(command);
				final Point from = commandMove.getMove().getFrom();
				final Point to = commandMove.getMove().getTo();	

				tableFrame.freezeCountDown();
				tableFrame.controller.moveProcedure(from , to);
			}
			else if (command.getName().equals(Commands.PLAYER_DISCONNECTED))
			{
				CommandPlayerDisconnected commandUserDisconnected = new CommandPlayerDisconnected(command);
				String usernameDisconnected = commandUserDisconnected.getUsername();
				Player playerDisconnected = table.getPlayerList().getPlayer(usernameDisconnected);

				// Check if the user that got disconnected is not a viewer, but a player
				if (playerDisconnected.isViewer() == false && model.isInPlay())
				{
					model.saveGameStateBeforePlayerDisconnected();
					
					// Set the game state to the one meaning we are waiting for a disconnected user to return
					model.setGameState(Model.GAME_STATE_WAITING_FOR_PLAYER);
					
					// Show a message that the user disconnected and that we are waiting for him to return
					tableFrame.setCountDownForPlayerReturn();
					
					String message = "The user \'" + usernameDisconnected + "\' has unexpectedly disconnected." + Settings.NEW_LINE +
									 "We will wait " + Settings.NUM_OF_SECS_WAIT_FOR_PLAYER + " seconds for him to return.";
					JOptionPane.showMessageDialog(tableFrame, message, "Player Disconnected", JOptionPane.INFORMATION_MESSAGE);
				}

				// Remove the player from the players list
				table.getPlayerList().removePlayer(usernameDisconnected);
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	public String getUsername(){
		return username;
	}
	

	public PlayerList getlayerList() {
		return table.getPlayerList();
	}

	@Override
	public void handleDisconnection() {
		if (model.getGameState() == Model.GAME_STATE_GAME_OVER)
		{
			return;
		}
		
		String message = "The user that opened this table, got disconnected. Sadly the game cannot be continued." + Settings.NEW_LINE;
		
		if (tableFrame.getModel().isPlayerViewer() == false)
		{
			message += "This will be considered as your win !";
			
			// Report to the server that this user has won the game
			CommandGameResult commandGameResult = new CommandGameResult(table.getOtherPlayerUsername(),table.getOwnerUsername());
			connRPSClientToServer.send(commandGameResult);
			
			CommandPlayerDisconnected commandPlayerDisconnected = new CommandPlayerDisconnected(table.getOwnerUsername(), table.getTableNum());
			connRPSClientToServer.send(commandPlayerDisconnected);
		}
		else
		{
			if (model.isInPlay())
			{
				message += "This will be considered to him as a lost and to the other player as a win.";
			}
			else if (model.getGameState() == Model.GAME_STATE_NOT_PLAYING)
			{
				message += "Please try another table.";
				tableFrame.disableStartButton();
			}
		}
		
		tableFrame.endGame();
		table.getPlayerList().removePlayer(table.getOwnerUsername());

		JOptionPane.showMessageDialog(tableFrame, message, "Game Over", JOptionPane.INFORMATION_MESSAGE);
	}

	@Override
	public Table getTable() {
		return table;
	}

	@Override
	public void leaveTable(CommandLeaveTable commandLeaveTable) {
		send(commandLeaveTable);
	}

	@Override
	public void broadcast(ITransmittable element) {
	}
	
	public void setTable(Table table) {
		this.table = table;
	}
	
	/**
	 * Called when either player presses the Start button (Which happens automatically for the owner). Checks if both players have already
	 * pressed the Start button and if so, disables the Start button for all the viewers and gets ready to play.
	 * 
	 * @param commandReadyToStart - The {@link CommandReadyToStart} command issued by the player's pressing the Start button.
	 */
	private void readyToStart(CommandReadyToStart commandReadyToStart) {
		String readyUsername = commandReadyToStart.getUsername(); 
		PlayerList players = table.getPlayerList();
		Player playerReadyToStart = players.getPlayer (readyUsername);
		if (playerReadyToStart == null) {
			return;
		}
		
		playerReadyToStart.readyToStart();

		// Check if this game is to start
		if (table.isReadyToStart()) {
			// Disable the Start button so that if I'm not a playing player, I won't be able to press Start
			tableFrame.disableStartButton();
			
			// If not the owner pressed Start (Which for him happens automatically), then set the joiner's name as the other player that
			// will be playing
			if (readyUsername.equals(table.getOwnerUsername()) == false)
			{
				table.setOtherPlayerUsername(readyUsername);
				tableFrame.setBluePlayerLabel(readyUsername);
			}

			Vector<Player> playersVector = table.getPlayerList().getPlayers();
			for (Player curPlayer : playersVector) {
				if (curPlayer.getPlayerState() == Player.PLAYER_STATE_READY_TO_START) {
					curPlayer.startGame();
				}
				else
				{
					curPlayer.setViewer();
				}
			}
			table.setGameState(Table.PLAYING);
			tableFrame.startGame();
		}
	}
	
	public TableFrame getTableFrame() {
		return tableFrame;
	}
	
	private void sendToUser(String usernameTo, String message)
	{
		CommandMessageToUser commandMessageToUser = new CommandMessageToUser(usernameTo, message);
		send(commandMessageToUser);
	}
	
	private void sendToUser(String usernameTo, ITransmittable transmittable)
	{
		sendToUser(usernameTo, transmittable.toXML().toString());
	}
}
